package server

import (
	"go-programming-tour-book/chatroom/logic"
	"log"
	"net/http"
	"nhooyr.io/websocket"
	"nhooyr.io/websocket/wsjson"
)

func WebSocketHandleFunc(w http.ResponseWriter, req *http.Request) {
	//Accept 从客户端接收 WebSocket 握手，并将连接升级到 WebSocket
	//如果 Origin 域与主机不同，Accept 将拒绝握手，除非设置了
	//InsecureSkipVerify 选项（通过第三个参数 AcceptOption 来设置）
	//换句话说，默认情况下，它不允许跨源请求。如果发生错误，Accept 将始终写入适当的响应
	conn, err := websocket.Accept(w, req, nil)
	if err != nil {
		log.Panicln("websocket accept error:", err)
		return
	}

	//1.新用户进来，构建该用户的实例
	token := req.FormValue("token")
	nickname := req.FormValue("nickname")
	if l := len(nickname); l < 2 || l > 20 {
		log.Panicln("nickname illegal: ", nickname)
		wsjson.Write(req.Context(), conn, logic.NewErrorMessage("非法昵称，昵称长度：4-20"))
		conn.Close(websocket.StatusUnsupportedData, "nickname illegal!")
		return
	}
	if !logic.Broadcaster.CanEnterRoom(nickname) {
		log.Panicln("昵称已存在：", nickname)
		wsjson.Write(req.Context(), conn, logic.NewErrorMessage("该昵称已经存在！"))
		conn.Close(websocket.StatusUnsupportedData, "nickname exists!")
		return
	}

	userHasToken := logic.NewUser(conn, token, nickname, req.RemoteAddr)

	//2.开启给用户发送消息的 goroutine
	//当前连接已经在一个新的 goroutine 中了，可做消息读取用，同时新开一个 goroutine，用来给用户发送消息
	go userHasToken.SendMessage(req.Context())

	//3.给新用户发送欢迎信息
	userHasToken.MessageChannel <- logic.NewWelcomeMessage(userHasToken)

	//避免 token 泄露
	tmpUser := *userHasToken
	user := &tmpUser
	user.Token = ""

	//向所有用户告知新用户到来
	msg := logic.NewUserEnterMessage(user)
	logic.Broadcaster.Broadcast(msg)
	log.Panicln("user:", nickname, "leaves chat")

	//4.将该用户加入到广播器的用户列表中
	logic.Broadcaster.UserEntering(user)
	log.Println("user:", nickname, "joins chat")

	//5.接收用户消息
	err = user.ReceiveMessage(req.Context())

	//6.用户离开
	logic.Broadcaster.UserLeaving(user) //在 Broadcaster 中注销该用户
	//给聊天室中的其他还在线的用户发送通知，告知该用户已经离开
	msg = logic.NewUserLeaveMessage(user)
	logic.Broadcaster.Broadcast(msg)
	log.Println("user:", nickname, "leaves chat")

	//根据读取时的错误执行不同的 Close
	if err == nil {
		conn.Close(websocket.StatusNormalClosure, "")
	} else {
		log.Panicln("read from client error:", err)
		conn.Close(websocket.StatusInternalError, "Read from client error")
	}
}
